<?php
/**
 ***********************************************************************************************************************
 * 访问规则
 * @author llq
 ***********************************************************************************************************************
 */
namespace soa\access\model\redis;
use artisan\config;

/**
 * Class rule
 * @package soa\access\model\redis
 */
class rule
{
    /**
     * 黑白名单规则键值前缀
     */
    const BLACKWHITE_PREFIX = 'access:blackwhite:';

    /**
     * IP频率规则键值前缀
     */
    const IP_PREFIX = 'access:limit:ip:';

    /**
     * ACCOUNT频率规则键值前缀
     */
    const ACCOUNT_PREFIX = 'access:limit:account:';

    /**
     * @var array
     */
    private $black_white_map = [
        'black' => 0,
        'black_segment' => 0,
        'white' => 1,
        'white_segment' => 1
    ];

    /**
     * 所有时段指标
     * @var array
     */
    private $indexes = [
        // 一分钟
        'one_minute',
        // 五分钟
        'five_minute',
        // 十五分钟
        'fifteen_minute',
        // 一小时
        'one_hour',
        // 六小时
        'six_hour',
        // 十二小时
        'twelve_hour',
        // 一天
        'one_day',
    ];

    /**
     *******************************************************************************************************************
     * 黑白名单处理
     *******************************************************************************************************************
     */

    /**
     * 对IP切片
     * @param $ip
     * @return array
     */
    private function sliceIp($ip)
    {
        if(!preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip)) {
            return [];
        }
        return [
            'first' => (string)substr($ip, 0, strrpos($ip, '.')),
            'last' => (string)substr($ip, strrpos($ip, '.') + 1, strlen($ip))
        ];
    }

    /**
     * 添加黑名单
     * @param $ip
     * @return bool|int
     */
    public function addBlack($ip)
    {
        if(empty($ip) || !is_string($ip) || !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip)) {
            return false;
        }
        if(empty($ip_arr = $this->sliceIp($ip))) {
            return false;
        }
        return $this->getRedis()->hset(
            self::BLACKWHITE_PREFIX . $ip_arr['first'],
            'last' . $ip_arr['last'],
            $this->black_white_map['black']
        );
    }

    /**
     * 添加黑名单段
     * @param $ip_segment
     * @return bool|int
     */
    public function addBlackSegment($ip_segment)
    {
        if(
            empty($ip_segment) ||
            !is_string($ip_segment) ||
            !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip_segment)
        ) {
            return false;
        }
        $key = self::BLACKWHITE_PREFIX . $ip_segment;
        $this->getRedis()->del($key);
        return $this->getRedis()->hset(
            $key,
            'all',
            $this->black_white_map['black_segment']
        );
    }

    /**
     * 添加白名单
     * @param $ip
     * @return bool|int
     */
    public function addWhite($ip)
    {
        if(empty($ip) || !is_string($ip) || !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip)) {
            return false;
        }
        if(empty($ip_arr = $this->sliceIp($ip))) {
            return false;
        }
        return $this->getRedis()->hset(
            self::BLACKWHITE_PREFIX . $ip_arr['first'],
            'last' . $ip_arr['last'],
            $this->black_white_map['white']
        );
    }

    /**
     * 添加白名单段
     * @param $ip_segment
     * @return bool|int
     */
    public function addWhiteSegment($ip_segment)
    {
        if(
            empty($ip_segment) ||
            !is_string($ip_segment) ||
            !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip_segment)
        ) {
            return false;
        }
        $key = self::BLACKWHITE_PREFIX . $ip_segment;
        $this->getRedis()->del($key);
        return $this->getRedis()->hset(
            $key,
            'all',
            $this->black_white_map['white_segment']
        );
    }


    /**
     * 删除黑名单
     * @return bool|int|string
     */
    public function delBlack($ip)
    {
        if(
            empty($ip) ||
            !is_string($ip) ||
            !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip)
        ) {
            return false;
        }
        if(empty($ip_arr = $this->sliceIp($ip))) {
            return false;
        }
        return $this->getRedis()->hDel(self::BLACKWHITE_PREFIX . $ip_arr['first'], 'last' . $ip_arr['last']);
    }

    /**
     * 删除黑名单段
     * @return bool|int|string
     */
    public function delBlackSegment($ip_segment)
    {
        if(
            empty($ip_segment) ||
            !is_string($ip_segment) ||
            !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip_segment)
        ) {
            return false;
        }
        return $this->getRedis()->hDel(self::BLACKWHITE_PREFIX . $ip_segment, 'all');
    }

    /**
     * 删除白名单
     * @return bool|int|string
     */
    public function delWhite($ip)
    {
        if(
            empty($ip) ||
            !is_string($ip) ||
            !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip)
        ) {
            return false;
        }
        if(empty($ip_arr = $this->sliceIp($ip))) {
            return false;
        }
        return $this->getRedis()->hDel(self::BLACKWHITE_PREFIX . $ip_arr['first'], 'last' . $ip_arr['last']);
    }

    /**
     * 删除白名单段
     * @return bool|int|string
     */
    public function delWhiteSegment($ip_segment)
    {
        if(
            empty($ip_segment) ||
            !is_string($ip_segment) ||
            !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip_segment)
        ) {
            return false;
        }
        return $this->getRedis()->hDel(self::BLACKWHITE_PREFIX . $ip_segment, 'all');
    }

    /**
     * 判断IP是否在黑白名单内
     * @param $ip
     * @return array
     */
    public function isBlackWhite($ip)
    {
        $ret = [
            'is_black' => false,
            'is_white' => false
        ];

        if(
            empty($ip) ||
            !is_string($ip) ||
            !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip) ||
            empty($ip_arr = $this->sliceIp($ip))
        ) {
            return $ret;
        }

        // 获取数据
        $hashes = $this->getBlackWhite($ip_arr['first']);
        // 黑名单
        if(
            (isset($hashes['all']) && $hashes['all'] == 0) ||
            (isset($hashes['last' . $ip_arr['last']]) && $hashes['last' . $ip_arr['last']] == 0)
        ) {
            $ret['is_black'] = true;
        }
        // 白名单
        if(
            (isset($hashes['all']) && $hashes['all'] == 1) ||
            (isset($hashes['last' . $ip_arr['last']]) && $hashes['last' . $ip_arr['last']] == 1)
        ) {
            $ret['is_white'] = true;
        }

        return $ret;
    }

    /**
     * 获取黑白名单原始数据
     * @param $ip_segment
     * @return array
     */
    public function getBlackWhite($ip_segment)
    {
        if(empty($ip_segment) || !is_string($ip_segment) || !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip_segment)) {
            return [];
        }
        return $this->getRedis()->hGetAll(self::BLACKWHITE_PREFIX . $ip_segment) ?: [];
    }

    /**
     *******************************************************************************************************************
     * 访问规则处理
     *******************************************************************************************************************
     */

    /**
     * 添加自定义IP访问规则
     * @param $service
     * @param $ip
     * @param $rule_index
     * @param int $cnt
     * @return bool|int
     */
    public function addIpRule($service, $ip, $rule_index, $cnt = 0)
    {
        if(
            empty($service) ||
            !is_string($service) ||
            empty($ip) ||
            !is_string($ip) ||
            !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip) ||
            empty($rule_index) ||
            !is_string($rule_index) ||
            !is_numeric($cnt)
        ) {
            return false;
        }
        return $this->getRedis()->hset(self::IP_PREFIX . $service . ':' . $ip, $rule_index, $cnt);
    }

    /**
     * 添加自定义账号访问规则
     * @param $service
     * @param $account
     * @param $rule_index
     * @param int $cnt
     * @return bool|int
     */
    public function addAccountRule($service, $account, $rule_index, $cnt = 0)
    {
        if(
            empty($service) ||
            !is_string($service) ||
            empty($account) ||
            !is_string($account) ||
            empty($rule_index) ||
            !is_string($rule_index) ||
            !is_numeric($cnt)
        ) {
            return false;
        }
        return $this->getRedis()->hset(self::ACCOUNT_PREFIX . $service . ':' . $account, $rule_index, $cnt);
    }

    /**
     * 根据IP获取访问规则
     * @param $service
     * @param $ip
     * @return array|bool
     */
    public function getRuleByIp($service, $ip)
    {
        if(
            empty($service) ||
            !is_string($service) ||
            empty($ip) ||
            !is_string($ip) ||
            !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip)
        ) {
            return [];
        }
        $rules = $this->getRedis()->hGetAll(self::IP_PREFIX . $service . ':' . $ip) ?: [];
        foreach($rules as $index => $rule) {
            if(!in_array($index, $this->indexes)) {
                unset($rules[$index]);
            }
        }
        return $rules;
    }

    /**
     * 根据账号获取访问规则
     * @param $service
     * @param $account
     * @return array
     */
    public function getRuleByAccount($service, $account)
    {
        if(
            empty($service) ||
            !is_string($service) ||
            empty($account) ||
            !is_string($account)
        ) {
            return [];
        }
        $rules = $this->getRedis()->hGetAll(self::ACCOUNT_PREFIX . $service . ':' . $account) ?: [];
        foreach($rules as $index => $rule) {
            if(!in_array($index, $this->indexes)) {
                unset($rules[$index]);
            }
        }
        return $rules;
    }

    /**
     * 根据IP删除访问规则
     * @param $service
     * @param $ip
     * @param string $rule_index
     * @return bool
     */
    public function delRuleByIp($service, $ip, $rule_index = '')
    {
        if(
            empty($service) ||
            !is_string($service) ||
            empty($ip) ||
            !is_string($ip) ||
            !preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $ip)
        ) {
            return false;
        }
        if(!empty($rule_index) && is_string($rule_index)) {
            return $this->getRedis()->hDel(self::IP_PREFIX . $service . ':' . $ip, $rule_index) ?: false;
        }
        return $this->getRedis()->del(self::IP_PREFIX . $service . ':' . $ip) ?: false;
    }

    /**
     * 根据账号删除访问规则
     * @param $service
     * @param $account
     * @param string $rule_index
     * @return bool
     */
    public function delRuleByAccount($service, $account, $rule_index = '')
    {
        if(
            empty($service) ||
            !is_string($service) ||
            empty($account) ||
            !is_string($account)
        ) {
            return false;
        }
        if(!empty($rule_index) && is_string($rule_index)) {
            return $this->getRedis()->hDel(self::ACCOUNT_PREFIX . $service . ':' . $account, $rule_index) ?: false;
        }
        return $this->getRedis()->del(self::ACCOUNT_PREFIX . $service . ':' . $account) ?: false;
    }

    /**
     * @var \artisan\cache\PHPredisDriver
     */
    private $redis;

    /**
     * 获取redis服务
     * @return \artisan\cache\PHPredisDriver
     */
    private function getRedis()
    {
        if(empty($this->redis)) {
            $this->redis = \artisan\cache::connect(config::get('access', 'redis'));
        }
        return $this->redis;
    }

}